#import "XDXGeofenceCoordinator.h"
#import "GeofenceInfo.h"
#import "WGS84TOGCJ02.h"

@interface XDXFenceState : NSObject
@property (nonatomic, assign) BOOL isInside;                 // 已确认状态
@property (nonatomic, strong) NSDate *lastTransitionAt;      // 上次确认时间（冷却）
@property (nonatomic, assign) BOOL desiredInside;            // 候选状态（GPS 去抖）
@property (nonatomic, strong) NSDate *desiredSince;          // 候选状态开始时间
@property (nonatomic, assign) XDXGeofenceSignal lastSignal;  // 最近确认来源

@property (nonatomic, assign) NSInteger consecInsideCount;   // 连续 inside 候选个数
@property (nonatomic, assign) NSInteger consecOutsideCount;  // 连续 outside 候选个数
@property (nonatomic, assign) BOOL lastRawInside;            // 上一次候选方向

@end

@implementation XDXFenceState
@end

// Persistence helpers for fence state
static NSURL *XDXFenceStateFileURL(void) {
    NSFileManager *fm = [NSFileManager defaultManager];
    NSURL *appSupport = [[fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject];
    NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier] ?: @"xdx";
    NSURL *dir = [appSupport URLByAppendingPathComponent:bundleId isDirectory:YES];
    [fm createDirectoryAtURL:dir withIntermediateDirectories:YES attributes:nil error:NULL];
    return [dir URLByAppendingPathComponent:@"geofence_state.json"];
}

static NSDictionary *XDXEncodeFenceState(XDXFenceState *st) {
    if (!st) return @{};
    NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:8];
    d[@"isInside"] = @(st.isInside);
    d[@"desiredInside"] = @(st.desiredInside);
    if (st.lastTransitionAt) d[@"lastTransitionAt"] = @([st.lastTransitionAt timeIntervalSince1970]);
    if (st.desiredSince) d[@"desiredSince"] = @([st.desiredSince timeIntervalSince1970]);
    d[@"lastSignal"] = @(st.lastSignal);
    d[@"consecInsideCount"] = @(st.consecInsideCount);
    d[@"consecOutsideCount"] = @(st.consecOutsideCount);
    d[@"lastRawInside"] = @(st.lastRawInside);
    return d;
}

static XDXFenceState *XDXDecodeFenceState(NSDictionary *d) {
    if (![d isKindOfClass:[NSDictionary class]]) return nil;
    XDXFenceState *st = [XDXFenceState new];
    st.isInside = [d[@"isInside"] boolValue];
    st.desiredInside = [d[@"desiredInside"] boolValue];
    NSNumber *lt = d[@"lastTransitionAt"];
    if ([lt isKindOfClass:[NSNumber class]]) {
        st.lastTransitionAt = [NSDate dateWithTimeIntervalSince1970:lt.doubleValue];
    }
    NSNumber *ds = d[@"desiredSince"];
    if ([ds isKindOfClass:[NSNumber class]]) {
        st.desiredSince = [NSDate dateWithTimeIntervalSince1970:ds.doubleValue];
    }
    NSNumber *ls = d[@"lastSignal"];
    if ([ls isKindOfClass:[NSNumber class]]) {
        st.lastSignal = (XDXGeofenceSignal)ls.integerValue;
    }
    NSNumber *ci = d[@"consecInsideCount"];
    if ([ci isKindOfClass:[NSNumber class]]) {
        st.consecInsideCount = ci.integerValue;
    }
    NSNumber *co = d[@"consecOutsideCount"];
    if ([co isKindOfClass:[NSNumber class]]) {
        st.consecOutsideCount = co.integerValue;
    }
    NSNumber *lr = d[@"lastRawInside"];
    if ([lr isKindOfClass:[NSNumber class]]) {
        st.lastRawInside = lr.boolValue;
    }
    return st;
}

static NSMutableDictionary<NSString *, XDXFenceState *> *XDXLoadFenceStates(void) {
    NSData *data = [NSData dataWithContentsOfURL:XDXFenceStateFileURL()];
    if (!data) return [NSMutableDictionary dictionary];
    NSError *err = nil;
    id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
    if (err || ![json isKindOfClass:[NSDictionary class]]) return [NSMutableDictionary dictionary];
    NSDictionary *root = (NSDictionary *)json;
    NSDictionary *states = root[@"states"];
    if (![states isKindOfClass:[NSDictionary class]]) return [NSMutableDictionary dictionary];
    NSMutableDictionary<NSString *, XDXFenceState *> *result = [NSMutableDictionary dictionaryWithCapacity:states.count];
    [states enumerateKeysAndObjectsUsingBlock:^(NSString *fenceId, id obj, BOOL *stop) {
        if (![fenceId isKindOfClass:[NSString class]]) return;
        XDXFenceState *st = XDXDecodeFenceState(obj);
        if (fenceId.length && st) {
            result[fenceId] = st;
        }
    }];
    return result;
}

static BOOL XDXSaveFenceStates(NSDictionary<NSString *, XDXFenceState *> *states) {
    if (!states) return NO;
    NSMutableDictionary *jsonStates = [NSMutableDictionary dictionaryWithCapacity:states.count];
    [states enumerateKeysAndObjectsUsingBlock:^(NSString *fenceId, XDXFenceState *st, BOOL *stop) {
        if (fenceId.length && st) {
            jsonStates[fenceId] = XDXEncodeFenceState(st);
        }
    }];
    NSDictionary *json = @{ @"version": @1, @"states": jsonStates };
    NSError *err = nil;
    NSData *data = [NSJSONSerialization dataWithJSONObject:json options:0 error:&err];
    if (!data || err) return NO;
    BOOL ok = [data writeToURL:XDXFenceStateFileURL() options:NSDataWritingAtomic error:&err];
    return ok && !err;
}

@interface XDXGeofenceCoordinator ()
@property (nonatomic, strong) NSDictionary<NSString *, GeofenceInfo *> *fencesById;
@property (nonatomic, strong) NSMutableDictionary<NSString *, XDXFenceState *> *stateById;
@property (nonatomic, strong) dispatch_queue_t fuseQueue;
@property (nonatomic, assign) NSInteger lastRegionSendTime; // 最近一次 发送围栏信息 时间戳
@end

@implementation XDXGeofenceCoordinator

- (instancetype)init {
    if (self = [super init]) {
        _fuseQueue = dispatch_queue_create("xdx.geo.fuse.queue", DISPATCH_QUEUE_SERIAL);
        _stateById = [NSMutableDictionary dictionary];
        _dwellConfirmSeconds = 30;
        _cooldownSeconds = 60;
        _minGPSAccuracyMeters = 100;
        _minConsecutiveSamples = 3;
        _hysteresisMeters = 30.0;
        _gpsDwellConfirmSeconds = 15.0;
        // Load persisted fence states if available
        NSMutableDictionary<NSString *, XDXFenceState *> *loaded = XDXLoadFenceStates();
        if (loaded.count > 0) {
            _stateById = loaded;
        }
        // 初始化围栏融合协调器
        if ([[[NSUserDefaults standardUserDefaults] objectForKey:LastRegionSendTime] integerValue]>0) {
            self.lastRegionSendTime = [[[NSUserDefaults standardUserDefaults] objectForKey:LastRegionSendTime] integerValue];
        }
    }
    return self;
}

#pragma mark - Public API

- (void)updateGeofences:(NSArray<GeofenceInfo *> *)geofences currentLocation:(CLLocation *)location {
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:geofences.count];
    for (GeofenceInfo *f in geofences) {
        if (!f) continue;
        if (!f.fenceId) continue;
        dict[f.fenceId] = f;
        if (!self.stateById[f.fenceId]) {
            self.stateById[f.fenceId] = [XDXFenceState new];
        }
    }
    self.fencesById = dict;
}

- (void)reconfigureMonitoredRegionsWithLocationManager:(CLLocationManager *)lm
                                       currentLocation:(CLLocation *)location
                                       maxRegionCount:(NSUInteger)maxCount {
    if (!lm) return;

    // 清理旧的
    for (CLRegion *r in lm.monitoredRegions) {
        [lm stopMonitoringForRegion:r];
    }

    NSArray<GeofenceInfo *> *sorted = [self sortedFencesByDistanceFrom:location];
    NSUInteger limited = MIN(MAX(1, maxCount), 20); // iOS 限制 20
    if (sorted.count == 0) return;
    NSArray<GeofenceInfo *> *top = [sorted subarrayWithRange:NSMakeRange(0, MIN(limited, sorted.count))];

    for (GeofenceInfo *f in top) {
        CLLocationDistance radius = f.radius;
        if (radius < 100.0) radius = 100.0;          // 过小半径易抖动
        if (radius > 2000.0) radius = 2000.0;      // 过大半径降低区分度
        CLLocationCoordinate2D loc = [WGS84TOGCJ02 transformFromGCJToWGS:f.center];
        CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:loc radius:radius identifier:f.fenceId];
        region.notifyOnEntry = (f.code == 1 || f.code == 3);
        region.notifyOnExit  = (f.code == 2 || f.code == 3);
        [lm startMonitoringForRegion:region];
        [lm requestStateForRegion:region];
    }
}

- (void)ingestVisit:(CLVisit *)visit {
    if (!visit) return;
    // 到达/离开：departureDate 在离开前为 distantFuture
    BOOL isDepartureEvent = ![[visit departureDate] isEqualToDate:[NSDate distantFuture]];
    CLLocation *loc = [[CLLocation alloc] initWithLatitude:visit.coordinate.latitude longitude:visit.coordinate.longitude];
    [self handleLocation:loc asSignal:(isDepartureEvent ? XDXGeofenceSignalVisit : XDXGeofenceSignalVisit) forceEmit:YES isArrival:!isDepartureEvent];
}

- (void)ingestRegion:(CLRegion *)region didEnter:(BOOL)didEnter {
    if (!region) return;
    GeofenceInfo *f = self.fencesById[region.identifier];
    if (!f) return;
    if (didEnter && !(f.code == 1 || f.code == 3)) return;
    if (!didEnter && !(f.code == 2 || f.code == 3)) return;
    [self emitIfNeededForFence:f newInside:didEnter source:XDXGeofenceSignalRegion force:YES];
}

- (void)ingestLocation:(CLLocation *)location {
    if (!location) return;
    if (location.horizontalAccuracy < 0) return;
    if (location.horizontalAccuracy > self.minGPSAccuracyMeters) return; // 精度不达标
    [self handleLocation:location asSignal:XDXGeofenceSignalGPS forceEmit:NO isArrival:NO];
}

#pragma mark - Internal

- (NSArray<GeofenceInfo *> *)sortedFencesByDistanceFrom:(CLLocation *)loc {
    if (!self.fencesById || self.fencesById.count == 0) return @[];
    NSArray<GeofenceInfo *> *all = self.fencesById.allValues;
    if (!loc) return all;
    return [all sortedArrayUsingComparator:^NSComparisonResult(GeofenceInfo *a, GeofenceInfo *b) {
        CLLocation *la = [[CLLocation alloc] initWithLatitude:a.center.latitude longitude:a.center.longitude];
        CLLocation *lb = [[CLLocation alloc] initWithLatitude:b.center.latitude longitude:b.center.longitude];
        CLLocationDistance da = [la distanceFromLocation:loc];
        CLLocationDistance db = [lb distanceFromLocation:loc];
        if (da < db) return NSOrderedAscending;
        if (da > db) return NSOrderedDescending;
        return NSOrderedSame;
    }];
}

- (void)handleLocation:(CLLocation *)aLocation asSignal:(XDXGeofenceSignal)signal forceEmit:(BOOL)force isArrival:(BOOL)isArrival {
    
    CLLocationCoordinate2D loc = [WGS84TOGCJ02 transformFromWGSToGCJ:[aLocation coordinate]];
    CLLocation *location = [[CLLocation alloc] initWithLatitude:loc.latitude longitude:loc.longitude];
   
    if (!self.fencesById || self.fencesById.count == 0) return;
    NSDate *now = [NSDate date];
    for (GeofenceInfo *f in self.fencesById.allValues) {
        CLLocation *fc = [[CLLocation alloc] initWithLatitude:f.center.latitude longitude:f.center.longitude];
        CLLocationDistance d = [fc distanceFromLocation:location];
        BOOL insideRaw = (d <= f.radius);
        
        // 可控的进/出过滤（根据 code）
        if (insideRaw == YES && !(f.code == 1 || f.code == 3)) {
            continue; // 不处理进入
        }
        if (insideRaw == NO && !(f.code == 2 || f.code == 3)) {
            continue; // 不处理离开
        }

        XDXFenceState *st = self.stateById[f.fenceId];
        if (!st) {
            st = [XDXFenceState new];
            self.stateById[f.fenceId] = st;
        }

        if (signal == XDXGeofenceSignalRegion || force) {
            // 强信号：立即触发，受冷却限制
            [self emitIfNeededForFence:f newInside:insideRaw source:signal force:YES];
        } else if (signal == XDXGeofenceSignalVisit) {
            // Visit 默认也可视为较强信号，但可加一点迟滞
            if (!st.desiredSince || st.desiredInside != insideRaw) {
                st.desiredInside = insideRaw;
                st.desiredSince = now;
            }
            NSTimeInterval dwell = [now timeIntervalSinceDate:st.desiredSince ?: now];
            if (dwell >= MAX(10.0, self.dwellConfirmSeconds * 0.6)) {
                [self emitIfNeededForFence:f newInside:insideRaw source:signal force:NO];
            }
        } else {
            // GPS：滞回 + 连续点 + 驻留
            double R = f.radius;
            double H = self.hysteresisMeters;
            BOOL insideCandidate;

            if (st.isInside) {
                insideCandidate = (d <= (R + H));
            } else {
                insideCandidate = (d <= (R - H));
            }

            if (!st.desiredSince || st.lastRawInside != insideCandidate) {
                st.desiredSince = now;
                st.consecInsideCount = 0;
                st.consecOutsideCount = 0;
            }
            if (insideCandidate) {
                st.consecInsideCount += 1;
            } else {
                st.consecOutsideCount += 1;
            }
            st.lastRawInside = insideCandidate;

            BOOL hitConsecutive = st.isInside ? (st.consecOutsideCount >= (NSInteger)self.minConsecutiveSamples)
                                              : (st.consecInsideCount  >= (NSInteger)self.minConsecutiveSamples);

            NSTimeInterval dwell = [now timeIntervalSinceDate:st.desiredSince ?: now];
            BOOL hitDwell = (dwell >= self.gpsDwellConfirmSeconds);

            if (hitConsecutive && hitDwell) {
                BOOL newInside = insideCandidate;
                BOOL entryAllowed = (f.code == 1 || f.code == 3);
                BOOL exitAllowed  = (f.code == 2 || f.code == 3);
                BOOL intendsEntry = (!st.isInside && newInside);
                BOOL intendsExit  = (st.isInside && !newInside);
                if ((intendsEntry && entryAllowed) || (intendsExit && exitAllowed)) {
                    [self emitIfNeededForFence:f newInside:newInside source:signal force:NO];
                    st.consecInsideCount = 0;
                    st.consecOutsideCount = 0;
                }
            }
        }
    }
}

- (void)emitIfNeededForFence:(GeofenceInfo *)f newInside:(BOOL)inside source:(XDXGeofenceSignal)source force:(BOOL)force {
    XDXFenceState *st = self.stateById[f.fenceId];
    if (!st) {
        st = [XDXFenceState new];
        self.stateById[f.fenceId] = st;
    }

    NSDate *now = [NSDate date];
    if (!force && st.lastTransitionAt) {
        NSTimeInterval cool = [now timeIntervalSinceDate:st.lastTransitionAt];
        if (cool < self.cooldownSeconds) {
            return; // 冷却窗口内不重复触发
        }
    }
    
    NSInteger nowTs = [[XDXTimeTool getNowTimeTimestamp] integerValue];
    if (nowTs-self.lastRegionSendTime>=ABNORMAL_DURATION) {
        self.lastRegionSendTime = nowTs;
        [[NSUserDefaults standardUserDefaults] setValue:@(self.lastRegionSendTime) forKey:LastRegionSendTime];
        [[NSUserDefaults standardUserDefaults] synchronize];
        if (st.isInside == inside) {
            return; // 状态未变化
        }
        st.isInside = inside;
        st.lastTransitionAt = now;
        st.lastSignal = source;

        // Persist local state before emitting event
        (void)XDXSaveFenceStates(self.stateById);

        if (self.onFenceEvent) {
            self.onFenceEvent(f, inside);
        }
    }
}

@end



